事件循环(event loop)
理解异步逻辑的前提是了解 JavaScript 的事件循环机制,它决定了代码的执行顺序。
在执行 JavaScript 时,Javascript 运行时实际上维护了一组用于执行 JavaScript 代码的代理。每一个代理由一组执行上下文的集合、执行上下文栈、主线程、一组可以创建用于执行 worker 的额外线程集合、一个人任务列队以及一个微任务列队构成。除了主线程(某些浏览器在多个代理之间共享的主线程)之外,其他组成部分对该代理都是唯一的。
每个代理都是由事件循环驱动的,事件循环负责收集事件(包括用户事件以及其他非用户事件)、对任务进行排队以便在何时的时候执行回调。然后它执行所有处于等待中的 JavaScript 任务,然后是微任务,然后再开始下一个循环之前执行一些必要的渲染和绘制工作。
一共有三种事件循环:
- window 事件循环: window 事件循环驱动所有共享同源的窗口
- worker 事件循环: worker 事件循环驱动 worker 的事件。包括所有的 worker ,基本的
web worker、shared worker、service worker - worklet 事件循环: worklet 事件循环驱动运行的 worklet 代理。包含
Worklet、AudioWorklet、PaintWorklet
任务和微任务
一个任务就是指计划由标准机制来执行的任何 JavaScript,如程序的初始化、事件触发的回调。
- 宏任务(Macrotask):由宿主环境(浏览器/Node.js)产生,执行一段程序,执行一个事件回调或一个
interval/timeout被触发子类的标准机制而被调度的任意 JavaScript 代码 。如:setImmediate、setTimeout、setInterval、DOM event、I/O event - 微任务(Microtask):由 JavaScript 引擎触发,将在当前任务完成其工作后运行,并且在执行上下文的控制权返回浏览器的事件循环之前没有其他代码等待运行时运行。如
Promise.then/catch/finally、MutationObserver(该接口提供了监视 DOM 树所更改的能力)、queueMicrotask(将微任务加入列队以在控制返回浏览器的事件循环前的安全事件执行)
任务队列和微任务队列的区别很简单,但却很重要:
- 当执行来自任务队列中的任务时,在每一次新的事件循环开始迭代的时候运行时都会执行队列中的每个任务。在每次迭代开始之后加入到队列中的任务需要在下一次迭代开始之后才会被执行。
- 每次当一个任务退出且执行上下文栈为空的时候,微任务队列中的每一个微任务会依次被执行。不同的是它会等到微任务队列为空才会停止执行——即使中途有微任务加入。换句话说,微任务可以添加新的微任务到队列中,这些新的微任务将在下一个任务开始运行之前,在当前事件循环迭代结束之前执行。
执行规则
- 每轮事件循环先执行当前宏任务
- 执行完当前宏任务后,清空所有的微任务列队(按入对的顺序执行)
- 最后执行下一个宏任务